/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cn.tedu.mall.seckill.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.pojo.product.vo.SpuDetailStandardVO;
import cn.tedu.mall.common.pojo.product.vo.SpuStandardVO;
import cn.tedu.mall.common.pojo.seckill.model.SeckillSpu;
import cn.tedu.mall.common.pojo.seckill.vo.SeckillSpuDetailSimpleVO;
import cn.tedu.mall.common.pojo.seckill.vo.SeckillSpuVO;
import cn.tedu.mall.common.restful.JsonPage;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.product.service.seckill.IForSeckillSpuService;
import cn.tedu.mall.seckill.mapper.SeckillSpuMapper;
import cn.tedu.mall.seckill.service.ISeckillSpuService;
import cn.tedu.mall.seckill.utils.RedisBloomUtils;
import cn.tedu.mall.seckill.utils.SeckillCacheUtils;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import java.math.BigDecimal;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.math.RandomUtils;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @since 2022-02-23
 */
@Service
@Slf4j
public class SeckillSpuServiceImpl implements ISeckillSpuService {
    @DubboReference
    private IForSeckillSpuService dubboSpuService;
    @Autowired
    private SeckillSpuMapper seckillSpuMapper;

    @Override
    public JsonPage<SeckillSpuVO> listSeckillSpus(Integer page, Integer pageSize) {
        //首先分页查询seckill_spu表格
        PageHelper.startPage(page, pageSize);
        List<SeckillSpu> seckillSpus = seckillSpuMapper.selectSeckillSpus();
        //封装数据返回
        List<SeckillSpuVO> seckillSpuVOs = new ArrayList<>();
        for (SeckillSpu seckillSpu : seckillSpus) {
            //循环查询数据,拿到秒杀展示价格,和spuId
            BigDecimal seckillListPrice = seckillSpu.getListPrice();
            Long spuId = seckillSpu.getSpuId();
            //生成新对象
            SeckillSpuVO seckillSpuVO = new SeckillSpuVO();
            //调用微服务获取pms数据
            SpuStandardVO spuStandardVO = dubboSpuService.getSpuById(spuId);
            //封装数据
            BeanUtils.copyProperties(spuStandardVO, seckillSpuVO);
            //数据中还缺少秒杀价钱,起始时间,结束时间
            seckillSpuVO.setGmtStart(seckillSpu.getGmtStart());
            seckillSpuVO.setGmtEnd(seckillSpu.getGmtEnd());
            seckillSpuVO.setSeckillListPrice(seckillListPrice);
            seckillSpuVOs.add(seckillSpuVO);
        }
        return JsonPage.restPage(new PageInfo<>(seckillSpuVOs));
    }

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedisBloomUtils redisBloomUtils;

    /**
     * 查询spuId的秒杀商品,考虑缓存穿透的问题
     *
     * @param spuId
     * @return
     */
    @Override
    public SeckillSpuVO getSeckillSpu(Long spuId) {
        //布隆过滤器先查看是否存在
        String bloomFilterKey = SeckillCacheUtils.getBloomFilterKey(LocalDate.now());
        log.info("当前布隆过滤器key值:{}" , bloomFilterKey);
        Boolean exists = redisBloomUtils.bfexists(bloomFilterKey, spuId + "");
        if (!exists) {
            throw new CoolSharkServiceException(ResponseCode.NOT_FOUND, "您访问的商品根本不存在");
        }
        //准备数据
        SeckillSpuVO seckillSpuVO = null;
        //检查缓存是否存在SeckillSpuVO
        String seckillSpuVOKey = SeckillCacheUtils.getSeckillSpuVOKey(spuId);
        String randCodeKey = SeckillCacheUtils.getRandCodeKey(spuId);
        if (redisTemplate.hasKey(seckillSpuVOKey)) {
            //从缓存获取返回对象
            seckillSpuVO = (SeckillSpuVO) redisTemplate.boundValueOps(seckillSpuVOKey).get();
            //对象非空,存储randCode
            if (seckillSpuVO == null) {
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND, "缓存有秒杀商品key但是没有数据");
            }
        } else {
            //要是缓存没有数据,查询seckill数据库,远程访问商品微服务
            SeckillSpu seckillSpu = seckillSpuMapper.selectSeckillSpuBySpuId(spuId);
            //秒杀数据库不存在数据,抛异常,说明没有这个秒杀的商品存在,是错误数据
            if (seckillSpu == null) {
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND, "您访问的秒杀商品不存在，而且布隆过滤器还没起作用");
            }
            //需要封装这个对象了,远程调用product
            SpuStandardVO spuStandardVO = dubboSpuService.getSpuById(spuId);
            //拿到秒杀价钱
            BigDecimal seckillListPrice = seckillSpu.getListPrice();
            //生成新对象
            seckillSpuVO = new SeckillSpuVO();
            //封装数据
            BeanUtils.copyProperties(spuStandardVO, seckillSpuVO);
            //数据中还缺少秒杀价钱,起始时间,结束时间
            seckillSpuVO.setGmtStart(seckillSpu.getGmtStart());
            seckillSpuVO.setGmtEnd(seckillSpu.getGmtEnd());
            seckillSpuVO.setSeckillListPrice(seckillListPrice);
            redisTemplate.boundValueOps(seckillSpuVOKey).set(seckillSpuVO, 1000 * 60 * 60 * 72 + RandomUtils.nextInt(1000 * 60 * 60 * 5), TimeUnit.MILLISECONDS);
        }
        LocalDateTime nowTime = LocalDateTime.now();
        Duration afterStart = Duration.between(nowTime, seckillSpuVO.getGmtStart());
        Duration beforeEnd = Duration.between(seckillSpuVO.getGmtEnd(), nowTime);
        //查询这个商品是否正在秒杀时间段之内，如果是，从redis后去随机数放到url参数拼接起来。
        if (afterStart.isNegative() && beforeEnd.isNegative()) {
            seckillSpuVO.setUrl("/seckill/" + redisTemplate.boundValueOps(randCodeKey).get());
        }
        return seckillSpuVO;
    }

    /**
     * 根据spuId查询spu detail
     *
     * @param spuId
     * @return
     */
    private static final String SECKILL_SPU_DETAIL_VO_PREFIX = "seckill_spu_detail_vo_";

    @Override
    public SeckillSpuDetailSimpleVO getSeckillSpuDetail(Long spuId) {
        //查询缓存是否存在,存在则使用
        String seckillSpuDetailVOKey = SECKILL_SPU_DETAIL_VO_PREFIX + spuId;
        SeckillSpuDetailSimpleVO seckillSpuDetailVO = null;
        if (redisTemplate.hasKey(seckillSpuDetailVOKey)) {
            seckillSpuDetailVO = (SeckillSpuDetailSimpleVO) redisTemplate.boundValueOps(seckillSpuDetailVOKey).get();
        } else {
            //调用微服务获取pms数据
            SpuDetailStandardVO spuDetailStandardVO = dubboSpuService.getSpuDetailById(spuId);
            if (spuDetailStandardVO == null) {
                throw new CoolSharkServiceException(ResponseCode.NOT_FOUND, "您访问的商品详情不存在");
            }
            //封装数据
            seckillSpuDetailVO = new SeckillSpuDetailSimpleVO();
            BeanUtils.copyProperties(spuDetailStandardVO, seckillSpuDetailVO);
            redisTemplate.boundValueOps(seckillSpuDetailVOKey).set(seckillSpuDetailVO, 1000 * 60 * 60 * 72 + RandomUtils.nextInt(1000 * 60 * 60 * 5), TimeUnit.MILLISECONDS);
        }
        return seckillSpuDetailVO;
    }
}
